﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_OCF_Parser.h".
//-------------------------------------------------------------------
/*
	OCF - Object Configuration File with C++ like syntax
*/

#include "Parser.h"

namespace UX_STL
{
namespace UXParser
{
	
	UX_ENUM_BIT_OPERATIONS( OCFParser::TChunk::EOperation );

/*
=================================================
	конструктор
=================================================
*/
	OCFParser::OCFParser ()
	{
	}
	
/*
=================================================
	деструктор
=================================================
*/
	OCFParser::~OCFParser ()
	{}
	
/*
=================================================
	
=================================================
*/
	void OCFParser::Free ()
	{
		delete this;
	}
	
/*
=================================================
	
=================================================
*/
	bool OCFParser::PreParseFile (uni_c_string sFileName)
	{
		return false;
	}

/*
=================================================
	
=================================================
*/
	void OCFParser::SetDirectories (const string_array_t &aDir)
	{
		_aDirectories = aDir;
	}

/*
=================================================
	проверить поддерживается ли файл парсером
=================================================
*/
	bool OCFParser::IsSupported (uni_c_string sFileExt)
	{
		return sFileExt.ICmp( "ocf" );
	}

/*
=================================================
	заполняет дерево объектов данными из файла
=================================================
*/
	bool OCFParser::ParseFile (RFilePtr pFile, config_node_t pNode, uint uSize)
	{
		CHECK_ERR( pFile.IsNotNull() );

		_aLog.Clear();
		_aParsedFiles.Clear();

		CHECK_ERR( _Parse( pFile, pNode, uSize ) );
		return true;
	}
	
/*
=================================================
	ищет файл среди предваритель распарсеных
	файлов и при нахождении добавляет его
	дерево данных в текущее дерево данных
=================================================
*/
	bool OCFParser::_FindInPreParsedFiles (const string &sName, const string &sPath, config_node_t pNode)
	{
		FileAddress	s_address1( sName ),
					s_address2( sPath + sName );

		FOR( i, _aPreParsed )
		{
			if ( s_address1 == _aPreParsed[i].sFileName or
				 s_address2 == _aPreParsed[i].sFileName )
			{
				if ( !_aPreParsed[i].bParseEnd )
				{
					_aLog.PushBack( string("file: \"") << _aPreParsed[i].sFileName.Full() << "\" parsed with errors, can't attach it as pre-parsed file" );
					UX_BREAK_POINT();
					THROW( 1 );
					return true;
				}

				CCfgUtils::Copy( _aPreParsed[i].sTypeTree.First(), pNode );
				return true;
			}
		}

		return false;
	}
	
/*
=================================================
	сравнивает адрес файла с уже загруженными
=================================================
*/
	inline bool OCFParser::_CompareFiles (RFilePtr pFile)
	{
		FileAddress	s_address( pFile->Name() );

		FOR( i, _aParsedFiles )
		{
			if ( _aParsedFiles[i].sFileName == s_address )
			{
				if ( _aParsedFiles[i].bOpened )
					return true;
				else {
					_aParsedFiles[i].bOpened = true;
					return false;
				}
			}
		}

		_aParsedFiles.PushBack( TParsedFile( s_address ) );
		return false;
	}
	
/*
=================================================
	ищет файл в директориях и возвращает
	указатель на него
=================================================
*/
	inline bool OCFParser::_OpenFile (uni_c_string sName, RFilePtr &pFile)
	{
		pFile = null;

		FOR( i, _aDirectories )
		{
			string	fname = _aDirectories[i];
			FileAddressUtils::AddNameToPath( fname, sName );

			if ( FileSystem::IsFileExist( fname ) )
			{
				HddRFilePtr	rfile = File::HddRFile::New();

				if ( rfile->Open( fname ) )
				{
					pFile = rfile;
					return true;
				}
			}

			pFile = null;
		}
		return false;
	}
	
/*
=================================================
	маски допустимых символов
=================================================
*/
	inline bool IsIdentifer (char c)
	{
		return ( c == '_' or (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z' ) );
	}

	inline bool IsWordSymbol (char c)
	{
		return ( IsIdentifer(c) or (c >= '0' and c <= '9') );
	}

	inline bool IsNumber (char c)
	{
		return ( (c >= '0' and c <= '9') or c == '-' or c == '+' );
	}
	
	inline bool IsNumber10 (char c)
	{
		return (c >= '0' and c <= '9');
	}

	inline bool IsNumber16 (char c)
	{
		return ( (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F') );
	}

	inline bool IsNumbSymbol (char c)
	{
		return ( (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F') or
				 c == '.' or c == '-' or c == 'x' or c == 'X' );
	}

	inline bool IsMathOp (char c)
	{
		return ( c == '(' or c == '~' or c == '!' );
	}

	inline bool IsWrong (char c)
	{
		return ( c != 0 and c != ';' );
	}

#ifdef __UX_DEBUG__
#	define DEBUG_LINE	<< ", source line: " << string().FormatI(__LINE__, 10)
#else
#	define DEBUG_LINE	
#endif
	
#ifdef __UX_DEBUG__
#	define ERROR_MSG( _text ) {\
			_aLog.PushBack( string("error (") << sData.uLine << ", " << ((usize)sData.pStr - sData.uNumChars) << \
							") - " << _text DEBUG_LINE << '.' ); \
			UX_BREAK_POINT(); \
			THROW( 1 ); \
		}
#else
#	define ERROR_MSG( _text ) {\
			_aLog.PushBack( string("error (") << sData.uLine << ", " << ((usize)sData.pStr - sData.uNumChars) << \
							") - " << _text DEBUG_LINE << '.' ); \
			THROW( 1 ); \
		}
#endif

#define WARNING_MSG( _text ) \
	_aLog.PushBack( string("warning (") << sData.uLine << ", " << ((usize)str - sData.uNumChars) << \
					") - " << _text DEBUG_LINE << '.' );



	static const string	s_aUnknownError			= "unknown error",					// неизвестная ошибка
						s_aWrongChar			= "wrong char",						// использован недопустимый символ
						s_aWrongOper			= "invalid operation",				// недопустимая операция
						s_aNoSemicolon			= "missing ';' before char",		// нехватает ; перед символом
						s_aUnknownIdentifer		= "unknown identifer",				// неизвестное слово(идентификатор)
						s_aNoPairForBracket		= "no pair for the bracket",		// нет пары для скобки
						s_aEOF					= "unexpected end of file",			// непредвиденый конец файла
						s_aCantParseFile		= "can't parse file",				// неудается прочесть файл
						s_aRequiredTypeName		= "required type name",				// ожидается имя типа
						s_aMathOp				= "math block error";				// ошибка при разборе математических операций
	
	
/*
=================================================
	ищет объект по имени и возвращает указатель
=================================================
*/
	inline bool OCFParser::_FindInNamespace (uni_c_string_array_t &aStr, CCfgBaseElement *&pObj, config_const_node_t pNode)
	{
		if ( !pNode.Valid() or aStr.Empty() )
			return false;
		
		CCfgBaseElement *	p_obj = null;
		
		FOR( i, pNode )
		{
			p_obj = pNode[i].Elem();

			switch ( p_obj->GetType() )
			{
				case e_config_object::NAMESPACE :
					if ( p_obj->GetFieldName() == aStr.Front() )
					{
						aStr.PopFront();
						return _FindInNamespace( aStr, pObj, pNode[i] );
					}

				case e_config_object::ENUM :
					if ( p_obj->GetFieldName() == aStr.Front() )
					{
						aStr.PopFront();
						return _FindInEnum( aStr, pObj, pNode[i] );
					}
					else
					if ( p_obj->GetFieldName().Empty() and aStr.Count() == 1 )
					{
						if ( _FindInEnum( aStr, pObj, pNode[i] ) )
							return true;
					}
					break;

				case e_config_object::CONSTANT :
					if ( aStr.Count() == 1 and p_obj->GetFieldName() == aStr.Front() )
					{
						if ( pNode[i].Count() != 1 )
							return false;

						pObj = pNode[i][0].Elem();
						return true;
					}
			};
		}
		return false;
	}


	inline bool OCFParser::_FindInEnum (uni_c_string_array_t &aStr, CCfgBaseElement *&pObj, config_const_node_t pNode)
	{
		if ( !pNode.Valid() or aStr.Count() != 1 )
			return false;
		
		CCfgBaseElement *	p_obj = null;

		FOR( i, pNode )
		{
			p_obj = pNode[i].Elem();

			ASSERT( p_obj->GetType() == e_config_object::VALUE or
					p_obj->GetType() == e_config_object::STRING );

			if ( p_obj->GetFieldName() == aStr.Front() )
			{
				pObj = p_obj;
				return true;
			}
		}
		return false;
	}


	inline bool OCFParser::_FindConst (uni_c_string_array_t &aStr, CCfgBaseElement *&pObj, config_const_node_t pNode, uint uDepth)
	{
		if ( !pNode.Valid() or aStr.Empty() )
			return false;

		if ( _FindInNamespace( aStr, pObj, pNode ) )
			return true;

		if ( uDepth == 0 )
		{
			switch ( pNode.Elem()->GetType() )
			{
				case e_config_object::ENUM :
					if ( _FindInEnum( aStr, pObj, pNode ) )
						return true;
			}
		}

		return _FindConst( aStr, pObj, pNode.Parent(), ++uDepth );
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ProcessSpacesAndComments (TParseData &sData)
	{
		const char *&	str = sData.pStr;

		while ( true )
		{
			// Space or Tab //
			while ( *str == 32 or *str == 9 )	{ ++str; }

			// New Line //
			if ( *str == 13 or *str == 10 )		// TODO: linux version
			{
				++str;
				if ( *str == 10 )	++str;
				sData.uNumChars = (usize)str;
				sData.uLine++;
				continue;
			}
			

			// One Line Comment //
			if ( *str == '/' and *(str + 1) == '/' )
			{
				while ( *str != 13 and *str != 10 )		// TODO: linux version
				{
					if ( *str == 0 ) break;
					++str;
				}
				continue;
			}


			// Multiline Comment //
			if ( *str == '/' and *(str + 1) == '*' )
			{
				str += 2;

				const usize	u_line = sData.uLine;

				while ( !( *str == '*' and *(str + 1) == '/' ) )
				{
					++str;

					// New Line //
					if ( *str == 13 or *str == 10 )		// TODO: linux version
					{
						++str;
						if ( *str == 10 )	++str;
						sData.uNumChars = (usize)str;
						sData.uLine++;
					}

					// EOF //
					if ( *str == 0 ) {
						ERROR_MSG( s_aNoPairForBracket << " '/*', from line: " << u_line );
					}
				}
				str += 2;
				continue;
			}

			break;
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_FindSemicolon (TParseData &sData)
	{
		const char *&			str		= sData.pStr;
		e_config_object::type	e_type	= sData.pNode.Elem() != null ?
											sData.pNode.Elem()->GetType() :
											e_config_object::UNKNOWN;

		_ProcessSpacesAndComments( sData );

		if ( *str == ';' )
		{
			++str;
			return;
		}

		if ( e_type == e_config_object::VALUE or
			 e_type == e_config_object::STRING )
		{
			ERROR_MSG( s_aNoSemicolon << ": '" << *str << '\'' );
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadObjectData (TParseData &sData)
	{
		const char *&	str	= sData.pStr;

		while ( true )
		{
			_ProcessSpacesAndComments( sData );

			// field_name, object, enum, const
			if ( IsIdentifer( *str ) )
			{
				string	s_ident;
				_ReadName( sData, s_ident );

				if ( _SwitchIdentifer( sData, s_ident, false ) ) {
					continue;
				}
				else {
					sData.sFieldName.SetString( s_ident );

					_ProcessSpacesAndComments( sData );
			
					// read field value
					if ( *str == '=' ) {
						++str;
						_ProcessSpacesAndComments( sData );
						_ReadValue( sData );
						_FindSemicolon( sData );
					}
					continue;
				}
			}
			else

			if ( *str == '}' ) {
				++str;
				sData.pNode = sData.pNode.Parent();

				if ( --sData.iHeight < 0 ) {
					ERROR_MSG( s_aNoPairForBracket << ": } " );
				}
				return;
			}
			else

			if ( *str == ';' ) {
				++str;
				continue;
			}

			if ( *str == 0 )
			{
				if ( sData.iHeight > 0 ) {
					ERROR_MSG( s_aEOF );
				}
				return;
			}
			else
			{
				ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
			}
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadValue (TParseData &sData)
	{
		const char *&	str	= sData.pStr;
		
		if ( IsMathOp( *str ) )		return _ReadMathOper( sData );
		if ( IsNumber( *str ) )		return _ReadNumber( sData );
		if ( *str == '"' )			return _ReadString( sData );

		if ( *str == '{' )				// init list
		{
			++str;
			
			config_node_t		p_node	= sData.pNode;
			CCfgObjectElem *	p_obj	= new CCfgObjectElem( sData.pNode, true );
			sData.pNode = p_obj->GetNode();
			p_obj->SetName( sData.sFieldName );

			_ReadArray( sData, e_config_object::OBJECT );
			sData.pNode = p_node;
			return;
		}

		if ( IsIdentifer( *str ) )		// object, array, const_name
		{
			string					s_ident;
			uni_c_string_array_t	a_ns;
			_ReadIdentifer( sData, s_ident, a_ns );

			if ( a_ns.Count() == 1 and _SwitchIdentifer( sData, s_ident, true ) )
				return;

			if ( _FindValue( sData, a_ns ) )
				return;

			ERROR_MSG( s_aUnknownIdentifer << ": \"" << s_ident << '"' );
		}
		
		ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
	}

/*
=================================================
	
=================================================
*/
	inline void RebuildNamespaces (const string &sIdent, uni_c_string_array_t &aNamespaces)
	{
		const char *	p_word = sIdent.cstr();

		usize	i = 0;
		for (; i < sIdent.Length(); ++i)
		{
			if ( sIdent[i] != '.' )
				continue;

			aNamespaces.PushBack( uni_c_string( p_word, PointerOffset( &sIdent[i], p_word ) ) );
			p_word = &sIdent[i+1];
		}
		aNamespaces.PushBack( uni_c_string( p_word, PointerOffset( &sIdent[i], p_word ) ) );
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadIdentifer (TParseData &sData, string &sIdent, uni_c_string_array_t &aNamespaces)
	{
		const char *&	str		= sData.pStr;
		const char *	p_word	= sData.pStr;
		
		while ( IsWordSymbol(*str) or *str == '.' )		{ ++str; }

		sIdent = string( p_word, PointerOffset( str, p_word ) );

		RebuildNamespaces( sIdent, aNamespaces );
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadName (TParseData &sData, string &sIdent)
	{
		const char *&	str	= sData.pStr;

		string					s_ident;
		uni_c_string_array_t	a_ns;
		_ReadIdentifer( sData, s_ident, a_ns );

		if ( a_ns.Count() == 1 ) {
			sIdent.SetString( s_ident );
			return;
		}

		ERROR_MSG( s_aUnknownIdentifer << ": \"" << s_ident << '"' );
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadString (TParseData &sData)
	{
		const char *&	str			= sData.pStr;
		const char *	p_word		= sData.pStr;
		string			s_tmp_str;

		while ( true )
		{
			if ( *str == '"' )
			{
				p_word = ++str;

				while ( *str != '"' and *str != 13 and *str != 10 and *str != 0 )	{ ++str; }

				if ( *str != '"' )
				{
					ERROR_MSG( s_aNoPairForBracket << " \"" );
				}
				
				const usize	len = (str++) - p_word;

				if ( len != 0 )
					s_tmp_str.Append( uni_c_string( p_word, len ) );
				
				_ProcessSpacesAndComments( sData );
				continue;
			}

			if ( *str == '+' )
			{
				++str;
				_ProcessSpacesAndComments( sData );

				if ( *str == '"' )
					continue;
				
				if ( IsIdentifer( *str ) )
				{
					string					s_ident;
					uni_c_string_array_t	a_ns;
					_ReadIdentifer( sData, s_ident, a_ns );
							
					CCfgBaseElement	*	p_temp	= null;
					bool				b_temp	= _FindConst( a_ns, p_temp, sData.pNode );

					if ( b_temp and p_temp != null )
					{
						e_config_object::type	e_type = p_temp->GetType();

						switch ( e_type )
						{
							case e_config_object::STRING :
								s_tmp_str.Append( ((CCfgStringElem *)p_temp)->GetStr() );
								break;

							// convert to string
							case e_config_object::VALUE :
								((CCfgNumberElem *)p_temp)->GetValue( s_tmp_str );
								break;

							default :
							{
								ERROR_MSG( s_aWrongOper << ": can't convert constant \"" << s_ident << "\" to string" );
							}
						}
					}
					else
					{
						ERROR_MSG( s_aUnknownIdentifer << ": \"" << s_ident << '"' );
					}
					
					_ProcessSpacesAndComments( sData );
					continue;
				}
				
				ERROR_MSG( s_aWrongChar << " '" << *str << '"' );
			}
			
			if ( *str == '\\' )
			{
				++str;
				_ProcessSpacesAndComments( sData );
				continue;
			}

			if ( *str == ';' or *str == ',' or *str == '}' )
			{
				break;
			}

			ERROR_MSG( s_aUnknownError );
		}

		CCfgBaseElement	*	p_obj = new CCfgStringElem( sData.pNode, s_tmp_str );
		p_obj->SetName( sData.sFieldName );
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadStringOp (TParseData &sData, CCfgBaseElement *pElem)
	{
		const char *&	str		= sData.pStr;
		
		while ( true )
		{
			_ProcessSpacesAndComments( sData );
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadNumber (TParseData &sData)
	{
		const char *&	str		= sData.pStr;
		const char *	p_word	= sData.pStr;
		usize			u_err_pos = -1;

		while ( IsNumbSymbol(*str) )		{ ++str; }

		CCfgBaseElement	*	p_obj = new CCfgNumberElem( sData.pNode, uni_c_string( p_word, (usize)str - (usize)p_word ), u_err_pos );
		p_obj->SetName( sData.sFieldName );

		if ( u_err_pos != (usize)-1 ) {
			ERROR_MSG( s_aWrongChar << " '" << *(p_word + u_err_pos) << '\'' );
		}

		_ProcessSpacesAndComments( sData );

		if ( *str == ';' or *str == ',' or *str == '}' )
			return;

		return _ReadMathOper( sData, p_obj );
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_UnaryOperation (TParseData &sData, int64 &res, TChunk::EOperation op)
	{
		switch ( op )
		{
			case TChunk::PLUS :			res = +res;		return;
			case TChunk::MINUS :		res = -res;		return;
			case TChunk::NOT :			res = !res;		return;
			case TChunk::INV :			res = ~res;		return;
		};
		
		ERROR_MSG( s_aMathOp << " unsupported unary math operation, code: 0x" << string().FormatI<int>( op, 16 ) );
		res = 0;
	}

	inline void OCFParser::_UnaryOperation (TParseData &sData, double &res, TChunk::EOperation op)
	{
		switch ( op )
		{
			case TChunk::PLUS :			res = +res;		return;
			case TChunk::MINUS :		res = -res;		return;
		};
		
		ERROR_MSG( s_aMathOp << " unsupported unary math operation, code: 0x" << string().FormatI<int>( op, 16 ) );
		res = 0;
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_BinaryOperation (TParseData &sData, int64 &res, TChunk::EOperation op, const int64 &l, const int64 &r)
	{
		switch ( op )
		{
			case TChunk::ADD :			res = l+r;		return;
			case TChunk::SUB :			res = l-r;		return;
			case TChunk::MUL :			res = l*r;		return;
			case TChunk::DIV :			res = l/r;		return;
			case TChunk::MOD :			res = l%r;		return;
			case TChunk::AND :			res = l&r;		return;
			case TChunk::OR :			res = l|r;		return;
			case TChunk::XOR :			res = l^r;		return;
			case TChunk::LESS :			res = l<r;		return;
			case TChunk::GREATHER :		res = l>r;		return;
			case TChunk::LOGIC_AND :	res = l&&r;		return;
			case TChunk::LOGIC_OR :		res = l||r;		return;
			case TChunk::EQUAL :		res = l==r;		return;
			case TChunk::NOT_EQUAL :	res = l!=r;		return;
			case TChunk::LEQUAL :		res = l<=r;		return;
			case TChunk::GEQUAL :		res = l>=r;		return;
			case TChunk::L_SHIFT :		res = l<<r;		return;
			case TChunk::R_SHIFT :		res = l>>r;		return;
		};
		
		ERROR_MSG( s_aMathOp << " unsupported binary math operation, code: 0x" << string().FormatI<int>( op, 16 ) );
		res = 0;
	}

	inline void OCFParser::_BinaryOperation (TParseData &sData, double &res, TChunk::EOperation op, const double &l, const double &r)
	{
		switch ( op )
		{
			case TChunk::ADD :			res = l+r;		return;
			case TChunk::SUB :			res = l-r;		return;
			case TChunk::MUL :			res = l*r;		return;
			case TChunk::DIV :			res = l/r;		return;
			case TChunk::MOD :			res = UXMath::Mod(l,r);		return;
			case TChunk::LESS :			res = l<r;		return;
			case TChunk::GREATHER :		res = l>r;		return;
			case TChunk::LOGIC_AND :	res = l&&r;		return;
			case TChunk::LOGIC_OR :		res = l||r;		return;
			case TChunk::EQUAL :		res = l==r;		return;
			case TChunk::NOT_EQUAL :	res = l!=r;		return;
			case TChunk::LEQUAL :		res = l<=r;		return;
			case TChunk::GEQUAL :		res = l>=r;		return;
		};
		
		ERROR_MSG( s_aMathOp << " unsupported binary math operation, code: 0x" << string().FormatI<int>( op, 16 ) );
		res = 0;
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ParseOperations (TParseData &sData, array<TChunk> &aChunks, usize uLastBracket)
	{
		const uint	c_uMaxPriority	= 11;
		usize		u_begin			= uLastBracket+1;
		uint		u_bit_mask		= 0;
		
		if ( aChunks.Back().type == TChunk::OPERATION and aChunks.Back().op == TChunk::L_BRACKET ) {
			ERROR_MSG( s_aMathOp << " no data between the brackets" );
		}


		// set priority and validate
		for (usize i = u_begin; i < aChunks.Count(); ++i)
		{
			if ( aChunks[i].type != TChunk::OPERATION )
				continue;

			if ( i+1 < aChunks.Count() and aChunks[i+1].type == TChunk::OPERATION )
			{
				// set unary + and -
				if ( aChunks[i+1].op == TChunk::ADD or aChunks[i+1].op == TChunk::SUB )
					aChunks[i+1].op &= ~TChunk::_BIN;
				else {
					ERROR_MSG( s_aMathOp << " two operators in a row" );
				}
			}
			else
			if ( i == u_begin or i+1 == aChunks.Count() )
			{
				// set unary + and -
				if ( i == u_begin and ( aChunks[i].op == TChunk::ADD or aChunks[i].op == TChunk::SUB ) )
					aChunks[i].op &= ~TChunk::_BIN;
				else {
					ERROR_MSG( s_aMathOp << " wrong operators location (missing left or right value)" );
				}
			}

			// set priority
			switch ( aChunks[i].op )
			{
				case TChunk::PLUS :
				case TChunk::MINUS :
				case TChunk::NOT :
				case TChunk::INV :
					aChunks[i].priority = c_uMaxPriority;
					u_bit_mask |= 1 << c_uMaxPriority;
					break;
				
				case TChunk::MUL :
				case TChunk::DIV :
				case TChunk::MOD :
					aChunks[i].priority = c_uMaxPriority - 1;
					u_bit_mask |= 1 << (c_uMaxPriority - 1);
					break;

				case TChunk::ADD :
				case TChunk::SUB :
					aChunks[i].priority = c_uMaxPriority - 2;
					u_bit_mask |= 1 << (c_uMaxPriority - 2);
					break;

				case TChunk::L_SHIFT :
				case TChunk::R_SHIFT :
					aChunks[i].priority = c_uMaxPriority - 3;
					u_bit_mask |= 1 << (c_uMaxPriority - 3);
					break;
				
				case TChunk::LESS :
				case TChunk::GREATHER :
				case TChunk::LEQUAL :
				case TChunk::GEQUAL :
					aChunks[i].priority = c_uMaxPriority - 4;
					u_bit_mask |= 1 << (c_uMaxPriority - 4);
					break;
				
				case TChunk::EQUAL :
				case TChunk::NOT_EQUAL :
					aChunks[i].priority = c_uMaxPriority - 5;
					u_bit_mask |= 1 << (c_uMaxPriority - 5);
					break;

				case TChunk::AND :
					aChunks[i].priority = c_uMaxPriority - 6;
					u_bit_mask |= 1 << (c_uMaxPriority - 6);
					break;

				case TChunk::XOR :
					aChunks[i].priority = c_uMaxPriority - 7;
					u_bit_mask |= 1 << (c_uMaxPriority - 7);
					break;

				case TChunk::OR :
					aChunks[i].priority = c_uMaxPriority - 8;
					u_bit_mask |= 1 << (c_uMaxPriority - 8);
					break;

				case TChunk::LOGIC_AND :
					aChunks[i].priority = c_uMaxPriority - 9;
					u_bit_mask |= 1 << (c_uMaxPriority - 9);
					break;

				case TChunk::LOGIC_OR :
					aChunks[i].priority = c_uMaxPriority - 10;
					u_bit_mask |= 1 << (c_uMaxPriority - 10);
					break;

				default :
					ERROR_MSG( s_aMathOp << " unsupported math operation, code: 0x" + string().FormatI<int>( aChunks[i].op, 16 ) );
			};

		
			if ( aChunks[i].op < TChunk::_UNARY )
			{
				ASSERT( aChunks[i+1].type == TChunk::VALUE );

				_UnaryOperation( sData, aChunks[i+1].value, aChunks[i].op );
				aChunks.Erase( i );
			}
		}


		for (int j = (int)c_uMaxPriority-1; j > 0; --j)
		{
			if ( u_begin+1 == aChunks.Count() )
				break;

			if ( EnumNoCmp( u_bit_mask, 1 << j ) )
				continue;

			for (usize i = u_begin; i < aChunks.Count(); ++i)
			{
				if ( aChunks[i].type == TChunk::OPERATION and aChunks[i].priority == j )
				{
					int64	res = 0;

					ASSERT( aChunks[i-1].type == TChunk::VALUE and aChunks[i+1].type == TChunk::VALUE );

					_BinaryOperation( sData, res, aChunks[i].op, aChunks[i-1].value, aChunks[i+1].value );

					aChunks[i-1] = TChunk( res );
					aChunks.Erase( i, 2 );
					i--;
				}
			}
		}

		if ( u_begin != 0 ) {
			aChunks[ uLastBracket ] = aChunks[ u_begin ];
			aChunks.Resize( uLastBracket+1 );
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadMathOper (TParseData &sData, CCfgBaseElement *pElem)
	{
		const char *&	str = sData.pStr;
		array<TChunk>	a_chunks;
		usize_array_t	a_brackets;

		if ( pElem != null )
		{
			ASSERT( pElem->GetType() == e_config_object::VALUE );
			a_chunks.PushBack( TChunk( ((CCfgNumberElem *)pElem)->GetInt64() ) );
		}

		for (; *str != 0; ++str)
		{
			_ProcessSpacesAndComments( sData );

			if ( IsIdentifer( *str ) )
			{
				string					s_ident;
				uni_c_string_array_t	a_ns;
				_ReadIdentifer( sData, s_ident, a_ns );
				--str;
				
				CCfgBaseElement	*	p_temp	= null;
				bool				b_temp	= _FindConst( a_ns, p_temp, sData.pNode );
				
				if ( b_temp and p_temp != null and p_temp->GetType() == e_config_object::VALUE )
				{
					a_chunks.PushBack( TChunk( ((CCfgNumberElem *)p_temp)->GetInt64() ) );
				}
				else
				{
					if ( b_temp and p_temp != null ) {
						ERROR_MSG( s_aUnknownError << "unsupported constant type, must be VALUE" );
					} else {
						ERROR_MSG( s_aUnknownIdentifer << ": \"" << s_ident << '"' );
					}
				}
			}
			else
			if ( IsNumber10( *str ) )
			{
				// read number
				const char	*p_begin = str;

				if ( *str == '0' and ( *(str+1) == 'x' or *(str+1) == 'X' ) )
				{
					str += 2;
					while ( *str != 0 and IsNumber16( *str ) )		{ ++str; }
					a_chunks.PushBack( TChunk( StringUtils::FromStr16ToInt( uni_c_string( p_begin, PointerOffset( str, p_begin ) ) ) ) );
				}
				else
				{
					while ( *str != 0 and IsNumber( *str ) )		{ ++str; }
					a_chunks.PushBack( TChunk( StringUtils::FromStr10ToInt( uni_c_string( p_begin, PointerOffset( str, p_begin ) ) ) ) );
				}
				--str;
			}
			else
			if ( *str == ';' or *str == ',' or *str == '}' )
				break;
			else
			{
				TChunk::EOperation	op = TChunk::EOperation(0);

				switch ( *str )
				{
					case '~' :	op = TChunk::INV;		break;
					case '!' :	op = TChunk::NOT;		break;
					case '&' :	op = TChunk::AND;		break;
					case '|' :	op = TChunk::OR;		break;
					case '^' :	op = TChunk::XOR;		break;
					case '+' :	op = TChunk::ADD;		break;
					case '-' :	op = TChunk::SUB;		break;
					case '*' :	op = TChunk::MUL;		break;
					case '/' :	op = TChunk::DIV;		break;
					case '%' :	op = TChunk::MOD;		break;
					case '<' :	op = TChunk::LESS;		break;
					case '>' :	op = TChunk::GREATHER;	break;
					case '=' :	op = TChunk::_EQUAL;	break;
					case '(' :	op = TChunk::L_BRACKET;	break;
					case ')' :	op = TChunk::R_BRACKET;	break;
					default  :
						ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
				};

				if ( op != 0 )
				{
					++str;

					switch ( *str )
					{
						case '=' :	op |= TChunk::_EQ;		break;
						case '&' :	op |= TChunk::_AND;		break;
						case '|' :	op |= TChunk::_OR;		break;
						case '<' :	op |= TChunk::_LESS;	break;
						case '>' :	op |= TChunk::_GREATHER;break;
						default  :	--str;
					};
				
					if ( op == TChunk::R_BRACKET )
					{
						_ParseOperations( sData, a_chunks, a_brackets.Back() );
						a_brackets.PopBack();
					}
					else
					if ( op == TChunk::L_BRACKET )
					{
						a_brackets.PushBack( a_chunks.Count() );
						a_chunks.PushBack( TChunk( op ) );
					}
					else
						a_chunks.PushBack( TChunk( op ) );
				}
			}
		}

		if ( !a_brackets.Empty() ) {
			ERROR_MSG( s_aNoPairForBracket << " '(', count: " << a_brackets.Count() );
		}

		if ( a_chunks.Count() > 1 )
			_ParseOperations( sData, a_chunks, (usize)-1 );

		ASSERT( !a_chunks.Empty() and a_chunks.Front().type == TChunk::VALUE );

		if ( pElem != null )
		{
			ASSERT( pElem->GetType() == e_config_object::VALUE );
			((CCfgNumberElem *)pElem)->SetValue( &a_chunks.Front().value, e_value::INT64 );
		}
		else
		{
			CCfgNumberElem	*	p_obj = new CCfgNumberElem( sData.pNode );
			p_obj->SetName( sData.sFieldName );
			p_obj->SetValue( &a_chunks.Front().value, e_value::INT64 );
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadArray (TParseData &sData, e_config_object::type eType)
	{
		const char *&	str		 = sData.pStr;
		int				i_height = 0;
		config_node_t	p_node	 = sData.pNode;
		string			s_type;

		if ( eType == e_config_object::ARRAY )
		{
			_ProcessSpacesAndComments( sData );

			if ( *str == '<' )
			{
				++str;
				_ProcessSpacesAndComments( sData );

				if ( IsIdentifer( *str ) )
				{
					const char *	p_word = str;
					while ( *str != 0 and IsWordSymbol( *(++str) ) ) {}

					s_type = string( p_word, PointerOffset( str, p_word ) );
				}
				
				_ProcessSpacesAndComments( sData );

				if ( *str != '>' ) {
					ERROR_MSG( s_aNoPairForBracket << " '>' in array element type" );
				}
				++str;
			}
		}

		while ( true )
		{
			_ProcessSpacesAndComments( sData );

			if ( *str == '{' )
			{
				CCfgBaseElement *	p_obj = null;

				if ( eType == e_config_object::ARRAY )
				{
					p_obj = new CCfgArrayElem( sData.pNode );
					
					if ( i_height == 0 and !s_type.Empty() )
						((CCfgArrayElem *)p_obj)->SetTypeName( s_type );
				}
				else
					p_obj = new CCfgBaseElement( sData.pNode, eType );
				
				sData.pNode = p_obj->GetNode();
				p_obj->SetName( sData.sFieldName );
				++i_height;
				++str;
				continue;
			}

			if ( *str == '}' )
			{
				++str;
				sData.pNode = sData.pNode.Parent();

				if ( --i_height <= 0 )
					break;

				_ProcessSpacesAndComments( sData );

				if ( *str == ',' )
					++str;

				continue;
			}
			
			_ReadValue( sData );
			_ProcessSpacesAndComments( sData );
			
			if ( *str == ',' ) {
				++str;
				continue;
			}

			if ( *str == '}' )
				continue;
			
			ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
		}

		sData.pNode = p_node;
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadObject (TParseData &sData)
	{
		const char *&	str		= sData.pStr;
		config_node_t	p_node	= sData.pNode;
		
		_ProcessSpacesAndComments( sData );

		if ( IsIdentifer( *str ) )
		{
			string					s_ident;
			uni_c_string_array_t	a_ns;
			_ReadIdentifer( sData, s_ident, a_ns );	// read object name

			CCfgBaseElement	*	p_obj 	= new CCfgObjectElem( sData.pNode, s_ident, a_ns );
			sData.pNode					= p_obj->GetNode();
			p_obj->SetName( sData.sFieldName );

			// find binding name
			_ProcessSpacesAndComments( sData );

			if ( *str == '(' )
			{
				const char *	p_word = ++str;

				while ( *str != ')' and *str != 13 and *str != 10 and *str != 0 )	{ ++str; }
				
				if ( *str != ')' ) {
					ERROR_MSG( s_aNoPairForBracket << ": ')' " );
				}

				((CCfgObjectElem *)p_obj)->SetBindingName( uni_c_string( p_word, PointerOffset( str, p_word ) ) );
				++str;
			}
		}
		else {
			ERROR_MSG( s_aRequiredTypeName << "from object" );	// no type of object
		}
		
		_ProcessSpacesAndComments( sData );

		if ( *str == '{' ) 
		{
			++str;
			++sData.iHeight;
			_ReadObjectData( sData );

			ASSERT( sData.pNode == p_node );
			sData.pNode = p_node;
			return;
		}
		else
		{
			ERROR_MSG( s_aNoPairForBracket << ": '}' " );	// require '{'
		}
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadNamespace (TParseData &sData)
	{
		const char *&	str		= sData.pStr;
		config_node_t	p_node	= sData.pNode;
		
		_ProcessSpacesAndComments( sData );

		if ( IsIdentifer( *str ) )
		{
			string	s_ident;
			_ReadName( sData, s_ident );	// read namespace name

			CCfgBaseElement	*	p_obj 	= new CCfgObjectElem( sData.pNode, s_ident, e_config_object::NAMESPACE );
			sData.pNode					= p_obj->GetNode();
		}
		else {
			ERROR_MSG( s_aRequiredTypeName << "from namespace" );
		}
		
		_ProcessSpacesAndComments( sData );

		if ( *str == '{' ) 
		{
			++str;
			++sData.iHeight;
			//_ReadObjectData( sData );
			
			while ( true )
			{
				_ProcessSpacesAndComments( sData );

				// field_name, object, enum, const
				if ( IsIdentifer( *str ) )
				{
					string	s_ident;
					_ReadName( sData, s_ident );

					if ( _SwitchIdentifer( sData, s_ident, false, true ) ) {
						continue;
					}
					else {
						ERROR_MSG( s_aUnknownIdentifer << ": \"" << s_ident << '"' );
					}
				}
				else

				if ( *str == '}' ) {
					++str;
					sData.pNode = sData.pNode.Parent();

					if ( --sData.iHeight < 0 ) {
						ERROR_MSG( s_aNoPairForBracket << ": '}' " );
					}
					return;
				}
				else

				if ( *str == ';' ) {
					++str;
					continue;
				}

				if ( *str == 0 )
				{
					if ( sData.iHeight > 0 ) {
						ERROR_MSG( s_aEOF );
					}
					return;
				}
				else
				{
					ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
				}
			}

			sData.pNode = p_node;
			return;
		}
		else
		{
			ERROR_MSG( s_aNoPairForBracket << ": '}' " );	// require '{'
		}
	}
		
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadConst (TParseData &sData)
	{
		const char *&	str	= sData.pStr;

		_ProcessSpacesAndComments( sData );
		
		if ( IsIdentifer( *str ) )
		{
			// read const name
			string	s_ident;
			_ReadName( sData, s_ident );

			config_node_t	p_node = sData.pNode;
			
			CCfgBaseElement *	p_obj = new CCfgBaseElement( sData.pNode, e_config_object::CONSTANT );
			p_obj->SetName( s_ident );
			sData.pNode = p_obj->GetNode();

			_ProcessSpacesAndComments( sData );
		
			// read const value
			if ( *str == '=' )
			{
				++str;
				_ProcessSpacesAndComments( sData );
				_ReadValue( sData );
				_FindSemicolon( sData );

				sData.pNode = p_node;
				return;
			}
		}
		
		ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
	}
		
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadEnum (TParseData &sData)
	{
		const char *&	str		= sData.pStr;
		config_node_t	p_node	= sData.pNode;

		_ProcessSpacesAndComments( sData );

		CCfgBaseElement *	p_obj = new CCfgBaseElement( sData.pNode, e_config_object::ENUM );
		sData.pNode = p_obj->GetNode();
		
		if ( IsIdentifer( *str ) )
		{
			// read enum type name
			string	s_ident;
			_ReadName( sData, s_ident );

			p_obj->SetName( s_ident );
			_ProcessSpacesAndComments( sData );
		}

		if ( *str == '{' )
		{
			++str;
			bool	b_exit = false;

			// read enum values
			while ( true )
			{
				_ProcessSpacesAndComments( sData );

				if ( *str == '}' or b_exit ) {
					sData.pNode = sData.pNode.Parent();
					++str;
					break;
				}
			
				string	s_ident;
				_ReadName( sData, s_ident );
				sData.sFieldName.SetString( s_ident );
					
				_ProcessSpacesAndComments( sData );

				if ( *str == '=' )
				{
					++str;
					_ProcessSpacesAndComments( sData );
					_ReadValue( sData );
					_ProcessSpacesAndComments( sData );

					if ( *str == ',' ) {
						++str;
						continue;
					}
				}
				else

				if ( *str == ',' or (b_exit = *str == '}') )
				{
					CCfgNumberElem *	p_obj = null;

					if ( !sData.pNode.Empty() )
						p_obj = new CCfgNumberElem( sData.pNode, sData.pNode[ sData.pNode.Count()-1 ].Elem(), true );
					else {
						const uint	i_value = 0;
						p_obj = new CCfgNumberElem( sData.pNode );
						p_obj->SetValue( &i_value, e_value::INT32 );
					}

					p_obj->SetName( sData.sFieldName );
					++str;
					continue;
				}
				
				if ( b_exit or (b_exit = *str == '}') )
					continue;

				ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
			}

			ASSERT( sData.pNode == p_node );
			sData.pNode = p_node;
		}
		else
		{
			ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
		}
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_ReadScript (TParseData &sData)
	{
		const char *&	str = sData.pStr;

		_ProcessSpacesAndComments( sData );

		if ( *str == '{' )
		{
			// TODO: parse script
			while ( *str != 0 and *str != '}' )	{ ++str; }

			if ( *str == '}' )
				++str;
		}
	}

/*
=================================================
	
=================================================
*/
	inline bool OCFParser::_SwitchIdentifer (TParseData &sData, const string &sIdent, bool bRightValue, bool bNoObj)
	{
		if ( sIdent == "object" and !bNoObj )	{ _ReadObject( sData );		return true; }
		if ( sIdent == "include" )				{ _AttachFile( sData );		return true; }

		if ( bRightValue )
		{
			if ( sIdent == "array" )			{ _ReadArray( sData, e_config_object::ARRAY );			return true; }
			if ( sIdent == "struct" or			// deprecated
				 sIdent == "binary_array" )		{ _ReadArray( sData, e_config_object::BINARY_ARRAY );	return true; }
			if ( sIdent == "variant_array" )	{ _ReadArray( sData, e_config_object::VARIANT_ARRAY );	return true; }
			if ( sIdent == "script" )			{ _ReadScript( sData );									return true; }
		}
		else
		{
			if ( sIdent == "const" )			{ _ReadConst( sData );		return true; }
			if ( sIdent == "enum" )				{ _ReadEnum( sData );		return true; }
			if ( sIdent == "namespace" )		{ _ReadNamespace( sData );	return true; }
			//if ( sIdent == "using" )			{ _ReadUsing( sData );		return true; }
		}
		
		return false;
	}

/*
=================================================
	
=================================================
*/
	inline bool OCFParser::_FindValue (TParseData &sData, uni_c_string_array_t &aIdent)
	{
		CCfgBaseElement	*	p_obj	= sData.pNode.Elem();
		const char *&		str		= sData.pStr;
		CCfgBaseElement	*	p_temp	= null;
		bool				b_temp	= _FindConst( aIdent, p_temp, sData.pNode );
						
		if ( b_temp and p_temp != null )
		{
			b_temp = CCfgUtils::CreateType( p_temp, sData.pNode, p_obj );
			p_obj->SetName( sData.sFieldName );

			_ProcessSpacesAndComments( sData );

			if ( *str == ';' or *str == ',' or *str == '}' )
				return true;

			_ReadMathOper( sData, p_obj );
			return true;
		}
		return false;
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_AttachFile (TParseData &sData)
	{
		_ProcessSpacesAndComments( sData );

		const char *&	str		= sData.pStr;
		const char *	p_word	= sData.pStr;

		p_word	= ++str;
		while ( *str != '"' and *str != 13 and *str != 10 and *str != 0 )	{ ++str; }

		if ( *str == '"' )
		{
			const usize		len = (str++) - p_word;

			if ( len == 0 ) {
				ERROR_MSG( s_aCantParseFile << ": file name is empty" );
			}
			else
			{
				const string	s_name( p_word, len );
				RFilePtr		p_file = null;

				if ( _FindInPreParsedFiles( s_name, sData.sFilePath, sData.pNode ) )
					return;

				if ( _OpenFile( s_name.cstr(), p_file ) or
					 _OpenFile( (sData.sFilePath + s_name).cstr(), p_file ) )
				{
					bool	b_res = _Parse( p_file, sData.pNode );

					p_file = null;

					if ( b_res )
						return;
				}
				ERROR_MSG( s_aCantParseFile << ": \"" << s_name << '"' );
			}
		}
		else
		{
			ERROR_MSG( s_aNoPairForBracket << " \"" );
		}
	}

/*
=================================================
	парсит файл
=================================================
*/
	bool OCFParser::_Parse (RFilePtr pFile, config_node_t pNode, const uint uSize)
	{
		if ( pFile == null )
			return false;
		
		const usize			u_size			= uSize ? uSize : pFile->Size() - pFile->Pos();
		binary_array_t		a_buf;
		TParseData			sData;
		const char *&		str				= sData.pStr;
		usize				u_preparse_idx	= usize(-1),
							u_parsed_idx	= usize(-1);


		if ( _CompareFiles( pFile ) )
		{
			_aLog.PushBack( string("file: \"") << pFile->Name() << "\" is missing." );
			return true;
		}
		else {
			sData.sFilePath = _aParsedFiles.Back().sFileName.Path();
			u_parsed_idx	= _aParsedFiles.Count()-1;
		}

		a_buf.Resize( u_size+1, false );
		_aLog.PushBack( string("parsing file: \"") << pFile->Name() << "\"." );

		if ( !pFile->Read( a_buf.ptr(), u_size ) )
		{
			_aLog.PushBack( "error while read file." );
			return false;
		}

		a_buf[ u_size ]	= 0;
		sData.pStr		= (char *)a_buf.ptr();
		sData.uLength	= a_buf.Count();
		sData.pNode		= pNode;


		TRY
		{
			const char *&	str = sData.pStr;

			_ProcessSpacesAndComments( sData );

			// parser directives
			if ( *str == '[' )
			{
				while ( *(++str) != 0 )
				{
					_ProcessSpacesAndComments( sData );
					
					// read key word
					if ( IsIdentifer( *str ) )
					{
						const char *p_word = str;

						while ( IsWordSymbol( *str ) ) { ++str; }

						string	s_key_word( p_word, PointerOffset( str, p_word ) );

						// add file to pre-parsed list
						if ( s_key_word == "static" ) {
							sData.eKeys = (EKeys::type)( sData.eKeys | EKeys::STATIC );
							u_preparse_idx = _aPreParsed.Count();
							_aPreParsed.PushBack( TPreParsedFile( pFile->Name() ) );
							sData.pNode = _aPreParsed.Back().sTypeTree.First();
						}
						else {
							ERROR_MSG( s_aUnknownIdentifer << ": \"" << s_key_word << '"' );
						}
					}
					else
					// next key word
					if ( *str == ',' )
						continue;
					// end of directives block
					if ( *str == ']' ) {
						++str;
						break;
					}
					else {
						ERROR_MSG( s_aWrongChar << " '" << *str << '\'' );
					}
				}
			}

			_ReadObjectData( sData );
		}
		CATCH( ...,
		{
			WARNING_MSG( s_aUnknownError );
			return false;
		} );
		
		_aLog.PushBack( string("parse end: \"") << pFile->Name() << "\"." );
		

		if ( EnumCmp( sData.eKeys, EKeys::STATIC ) )
		{
			_aPreParsed[u_preparse_idx].bParseEnd = true;
			CCfgUtils::Copy( _aPreParsed[u_preparse_idx].sTypeTree.First(), pNode );
		}

		_aParsedFiles[ u_parsed_idx ].bOpened = false;

		return true;
	}
	
/*
=================================================
	сохраняет в файл
=================================================
*/
	bool OCFParser::Save (WFilePtr pFile, config_const_node_t pNode)
	{
		CHECK_ERR( pFile != null );

		string	str;

		TRY {
			FOR( i, pNode ) {
				_s_RecursiveSave( str, pNode[i], 1 );
			}
		}
		CATCH( ...,
		{
			RETURN_ERR( "catching error while save config file" );
		} );

		CHECK_ERR( pFile->Write( str.cstr(), (uint)str.Length() ) );
		return true;
	}
	
/*
=================================================
	
=================================================
*/
	inline void OCFParser::_s_SaveNumber (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgNumberElem *	p_obj	= (const CCfgNumberElem *)pNode.Elem();
		bool					b_field = false;

		str << '[' << iHeight << ']';
		
		if ( !p_obj->GetFieldName().Empty() ) {
			str << p_obj->GetFieldName() << " = ";
			b_field = true;
		}

		p_obj->GetValue( str );
		
		if ( b_field )
			str << ";\n";

		FOR( i, pNode ) {
			_s_RecursiveSave( str, pNode[i], iHeight+1 );
		}
	}


	inline void OCFParser::_s_SaveString (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgStringElem *	p_obj = (const CCfgStringElem *)pNode.Elem();
		bool					b_field = false;

		str << '[' << iHeight << ']';
		
		if ( !p_obj->GetFieldName().Empty() ) {
			str << p_obj->GetFieldName() << " = ";
			b_field = true;
		}

		str << '"' << p_obj->GetStr() << '"';
		
		if ( b_field )
			str << ";\n";

		FOR( i, pNode ) {
			_s_RecursiveSave( str, pNode[i], iHeight+1 );
		}
	}
	

	inline void OCFParser::_s_SaveConst (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgBaseElement *	p_obj = pNode.Elem();

		str << '[' << iHeight << ']' << "const " << p_obj->GetFieldName() << " = ";

		FOR( i, pNode ) {
			_s_RecursiveSave( str, pNode[i], iHeight+1 );
		}

		str << ";\n";
	}
	

	inline void OCFParser::_s_SaveObject (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgObjectElem *	p_obj = (const CCfgObjectElem *)pNode.Elem();

		str << '[' << iHeight << ']';
		
		if ( !p_obj->GetFieldName().Empty() )
			str << p_obj->GetFieldName() << " = ";

		if ( !p_obj->IsInitList() )
		{
			str << "object " << p_obj->GetTypeName() << " {\n";

			FOR( i, pNode ) {
				_s_RecursiveSave( str, pNode[i], iHeight+1 );
			}
		}
		else
		{
			str << " {\n";

			FOR( i, pNode ) {
				_s_RecursiveSave( str, pNode[i], iHeight+1 );
				str << ", ";
			}
			str << "\n";
		}
		str << '[' << iHeight << ']' << "}\n";
	}


	inline void OCFParser::_s_SaveEnum (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgBaseElement *	p_obj = pNode.Elem();

		str << '[' << iHeight << ']' << "enum " << p_obj->GetFieldName() << " {\n";
		
		FOR( i, pNode ) {
			_s_RecursiveSave( str, pNode[i], iHeight+1 );
			str.PopBack();
			str.PopBack();
			str << ",\n";
		}
		
		str << '[' << iHeight << ']' << "}\n";
	}


	inline void OCFParser::_s_SaveArray (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgBaseElement *	p_obj = pNode.Elem();

		str << '[' << iHeight << ']';
		
		if ( !p_obj->GetFieldName().Empty() )
			str << p_obj->GetFieldName() << " = ";

		switch ( p_obj->GetType() )
		{
			case e_config_object::ARRAY :			str << "array";			break;
			case e_config_object::BINARY_ARRAY :	str << "binary_array";	break;
			case e_config_object::VARIANT_ARRAY :	str << "variant_array";	break;
		};

		str << " {\n";

		FOR( i, pNode ) {
			_s_RecursiveSave( str, pNode[i], iHeight+1 );
			str << ", ";
		}

		if ( !pNode.Empty() ) {
			str.PopBack();
			str.PopBack();
		}

		str << '\n' << '[' << iHeight << ']' << "}\n";
	}


	inline void OCFParser::_s_SaveNamespace (string &str, config_const_node_t pNode, int iHeight)
	{
		const CCfgObjectElem *	p_obj = (const CCfgObjectElem *)pNode.Elem();

		str << '[' << iHeight << ']';

		str << "namespace " << p_obj->GetFieldName() << " {\n";

		FOR( i, pNode ) {
			_s_RecursiveSave( str, pNode[i], iHeight+1 );
		}

		str << '[' << iHeight << ']' << "}\n";
	}

/*
=================================================
	
=================================================
*/
	inline void OCFParser::_s_RecursiveSave (string &str, config_const_node_t pNode, int iHeight)
	{
		if ( !pNode.Valid() or pNode.Elem() == null )
			return;

		switch ( pNode.Elem()->GetType() )
		{
			case e_config_object::ARRAY :
			case e_config_object::BINARY_ARRAY :
			case e_config_object::VARIANT_ARRAY :
				_s_SaveArray( str, pNode, iHeight );
				break;

			case e_config_object::CONSTANT :
				_s_SaveConst( str, pNode, iHeight );
				break;

			case e_config_object::ENUM :
				_s_SaveEnum( str, pNode, iHeight );
				break;

			case e_config_object::OBJECT :
				_s_SaveObject( str, pNode, iHeight );
				break;

			case e_config_object::NAMESPACE :
				_s_SaveNamespace( str, pNode, iHeight );
				break;

			case e_config_object::STRING :
				_s_SaveString( str, pNode, iHeight );
				break;

			case e_config_object::VALUE :
				_s_SaveNumber( str, pNode, iHeight );
				break;
		};
	}

//-------------------------------------------------------------------

}	// UXParser
}	// UX_STL
